/*
 * Copyright (c) 2011 Junichiro Okuno
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Junichiro Okuno.
 */

/**
 * TCP Server's Handler, pre-allocation scheme.
 * Each Thread waits in its accept() call for a connection; this synchronizes
 * on the serversocket when calling its accept() method.
 * @author J. Okuno
 * Reference: Darwin. I F (2002). Java Cookbook, O'Reilly
 */

package tcp_net;

import tcp_net.debug.DebugInfo;
import java.net.*;
import java.io.*; 

/** 
	A Thread subclass to handle one client conversation. 
*/
public abstract class Handler extends Thread {
		
		/**
			Debug information
		*/
		private final static boolean DEBUG = DebugInfo.HANDLER_DEBUG;
		
		/**
			A server socket
		*/
		protected ServerSocket servSock;
		
		/**
			A handler thread number
		*/
		protected int threadNumber;
		
		/**
			true if the handler thread closed the coneection
		*/
		private boolean isCloseSet;
		
		/**
			true if the handler thread has started
		*/
		private boolean isStarted;
		
		/**
			true if the handler thread needs to be terminated
		*/
		private boolean stop;
	
		/** 
			Construct a Handler.
			@param s a server network socket
			@param i a handler thread number 
		*/
		public Handler(ServerSocket s, int i) 
		{
			super();
			servSock = s;
			threadNumber = i;
			setName("Thread " + threadNumber);
			
			isCloseSet = false;
			isStarted= true;
			stop = false;
			DebugInfo.print(DEBUG,"Handler created. Thread No.:"+threadNumber);
		}
		
		/**
			This method provides to return a handler thread number
			@return a handler thread number 
		*/
		public int getThreadNumber() { return threadNumber; }
		
		/**
			This method provides to request a termination of this handler thread
		*/
		public void stopThread() 
		{ 
			DebugInfo.print(DEBUG, getName() + " stopThread()");
			stop = true; 
		}
		
		/**
			This method provides a status of this handler thread
			@return true if the Handler thread has already been started
		*/
		public boolean started() { return isStarted; }
		
		/**
			This method provides a status of this handler's connection
			@return true if the Handler's connection is closed
		*/
		public boolean closed() { return isCloseSet; }

		/**
			This method provides to terminate a handler thread
		*/
		public void setCloseConnection()
		{
			DebugInfo.print(DEBUG, getName() + " setCloseConnection()");
			isCloseSet = true;
		}
	
		/**
			This method provides to start another new handler thread
		*/
		protected abstract void startAnotherHandler();
		
		/**
			This method provides to terminate a client socket connection
			@param clientSocket a connected client network socket
		*/
		protected void close(Socket clientSocket)
		{
			try
			{
				clientSocket.close();
				DebugInfo.print(DEBUG, "Handler "+ threadNumber +" Client Socket closed.");
			}
			catch (IOException ex) {
				DebugInfo.print(DEBUG, getName() + ": IO Error on socket " + ex);
				return;
			}
		}
		
		/**
			This method provides to start a handler thread
			@param msg_processor A message processor for processing a command from the connected client
		*/
		protected void run(MessageProcessor msg_processor) 
		{
			this.run(msg_processor , null);
		}
		
		/**
			This method provides to start a handler thread
			@param msg_processor A message processor for processing a command from the connected client
			@param init_msg A string text for sending to the connected client as an initial greeting 
		*/
		protected void run(MessageProcessor msg_processor, String init_msg)
		{
			/* Wait for a connection. Synchronized on the ServerSocket
			 * while calling its accept() method. */
			while (!stop){
				try {
					DebugInfo.print(DEBUG, getName() + " waiting");
					
					Socket clientSocket;
					
					// Wait here for the next connection.
					synchronized(servSock) {
						clientSocket = servSock.accept();
					}
					startAnotherHandler();
					
					DebugInfo.print(DEBUG, getName() + " starting, IP=" + clientSocket.getInetAddress());
					
					DataInputStream is = new DataInputStream(clientSocket.getInputStream());
					PrintStream os = new PrintStream(clientSocket.getOutputStream(), true);
					
					if(init_msg!=null)
					{
						os.print(init_msg + "\r\n");
						os.flush();
					}
					
					String line;
					while ((line = is.readLine()) != null) 
					{
						DebugInfo.print(DEBUG, "Server Input: "+line);
						java.util.List<String> outputs = msg_processor.process(line);
						
						if(outputs != null)
						{
							for(String str : outputs)
							{
								DebugInfo.print(DEBUG, "Server Output: "+str);
								os.print(str + "\r\n"); //Sends line back to the connected client
								os.flush();
							}
						}
						
						if(isCloseSet) { close(clientSocket); return; }
					}
					
				} catch (IOException ex) {
					DebugInfo.print(DEBUG, "Handler: "+ getName() + ": IO Error on socket " + ex);
					return;
				}
			}
		}
}
